import * as d3 from "d3";
import { ChartData, ChartDataGroup, ChartDataMap, ChartScale, ChartScaleGroup, ChartScaleMap, ChartSetting, DataMap, Scaler } from "../type";
import { DataSource, DataSourceGroup, DataSourceUnion } from "../typeSC";

interface ScalerOptions {
    type: 'Date' | 'Liner',
    domain?:  string[] | number[],
    isReverse: boolean,
    isNice?: boolean,
}

export default class SVGScaler implements Scaler {
    chartDataMap: ChartDataMap = {};
    chartScaleMap: ChartScaleMap = {};

    constructor(DataSourceUnionList: DataSourceUnion[], dataMap: DataMap) {
        for (let DataSourceUnionItem of DataSourceUnionList) {
            switch (DataSourceUnionItem.type) {
                case 'DS': {
                    const id = DataSourceUnionItem.id;

                    const chartData: ChartData = this.calculateData(DataSourceUnionItem, dataMap);
                    this.chartScaleMap[id] = this.calculateScale(DataSourceUnionItem, chartData);
                    this.chartDataMap[id] = chartData;
                    break;
                }
                case 'DSG': {
                    const id = DataSourceUnionItem.id;

                    const chartDataGroup: ChartDataGroup = this.calculateDataGroup(DataSourceUnionItem, dataMap);
                    this.chartScaleMap[id] = this.calculateScaleGroup(DataSourceUnionItem, chartDataGroup);
                    this.chartDataMap[id] = chartDataGroup;
                }
            }
        }
    }

    public getScalers(scalerId: string) {
        if (scalerId.includes(':')) {
            const idArr = scalerId.split(':');

            const chartScale = this.chartScaleMap[idArr[0]];

            if ('childMap' in chartScale) {
                return chartScale.childMap[idArr[1]] ?? null;
            } else return null;
        } else {
            return this.chartScaleMap[scalerId] as ChartScale ?? null
        }
    }

    public getData(dataId: string) {
        if (dataId.includes(':')) {
            const idArr = dataId.split(':');

            const chartData = this.chartDataMap[idArr[0]];

            if ('childMap' in chartData) {
                return chartData.childMap[idArr[1]] ?? null;
            } else return null;
        } else {
            return this.chartDataMap[dataId] as ChartData ?? null
        }
    }

    private calculateDataGroup(dataSource: DataSourceGroup, dataMap: DataMap) {
        const xType = dataSource['axis_type'].x;
        const yType = dataSource['axis_type'].y;

        const chartDataMap: { [key: string]: ChartData } = {};

        for (let dataSourceItem of dataSource['data_sources']) {
            const id = dataSourceItem.id;

            const xKey = dataSourceItem['x_key'];
            const yKey = dataSourceItem['y_key'];
    
            const rawData = dataMap[dataSourceItem['array_key']];
    
            const data = this.convertArr(rawData, { x: xKey, y: yKey }, { x: xType, y: yType });

            chartDataMap[id] = data;
        }

        const data: { x: any, y: any }[][] = [];
        const x: any[][] = [];
        const y: any[][] = [];

        Object.values(chartDataMap).forEach(item => {
            data.push(item.data);
            x.push(item.x);
            y.push(item.y)
        });

        const dataGroup: ChartDataGroup = {
            data: data.flat(),
            x: x.flat(),
            y: y.flat(),

            childMap: chartDataMap,
        }

        return dataGroup;
    }

    private calculateScaleGroup(dataSource: DataSourceGroup, chartDataGroup: ChartDataGroup) {
        const xOptions = {
            type: dataSource['axis_type'].x,
            domain:  dataSource['domain']?.x,
            isReverse: dataSource['x_reverse'] ?? false,
            isNice: dataSource['nice']?.x,
        };

        const yOptions = {
            type: dataSource['axis_type'].y,
            domain:  dataSource['domain']?.y,
            isReverse: dataSource['y_reverse'] ?? false,
            isNice: dataSource['nice']?.y,
        };

        const chartScaleMap: { [key: string]: ChartScale } = {};
        for (let [id, chartData] of Object.entries(chartDataGroup.childMap)) {
            chartScaleMap[id] = {
                x: (range: [number, number]) => this.getScale(range, chartData.x, xOptions),
                y: (range: [number, number]) => this.getScale(range, chartData.y, yOptions),
            }
        }

        const chartScaleGroup: ChartScaleGroup = {
            x: (range: [number, number]) => this.getScale(range, chartDataGroup.x, xOptions),
            y: (range: [number, number]) => this.getScale(range, chartDataGroup.y, yOptions),

            childMap: chartScaleMap,
        }


        return chartScaleGroup;
    }

    private calculateData(dataSource: DataSource, dataMap: DataMap) {
        const xType = dataSource['axis_type'].x;
        const yType = dataSource['axis_type'].y;

        const xKey = dataSource['x_key'];
        const yKey = dataSource['y_key'];

        const rawData = dataMap[dataSource['array_key']];

        const data = this.convertArr(rawData, { x: xKey, y: yKey }, { x: xType, y: yType });

        return data;
    }

    private calculateScale(dataSource: DataSource, chartData: ChartData) {
        const xOptions = {
            type: dataSource['axis_type'].x,
            domain:  dataSource['domain']?.x,
            isReverse: dataSource['x_reverse'] ?? false,
            isNice: dataSource['nice']?.x,
        };

        const yOptions = {
            type: dataSource['axis_type'].y,
            domain:  dataSource['domain']?.y,
            isReverse: dataSource['y_reverse'] ?? false,
            isNice: dataSource['nice']?.y,
        };

        const chartScale: ChartScale = {
            x: (range: [number, number]) => this.getScale(range, chartData.x, xOptions),
            y: (range: [number, number]) => this.getScale(range, chartData.y, yOptions)
        }

        return chartScale;
    }

    private convertArr(arr: any, keys: { x: string, y: string }, types: { x: string, y: string }) {
        const data: any = [];
        const xArr: any = [];
        const yArr: any = [];

        for(let point of arr) {
            const x = this.convertValue(point[keys.x], types.x);
            const y = this.convertValue(point[keys.y], types.y);
            const newPoint = { x, y };

            xArr.push(x);
            yArr.push(y);
            data.push(newPoint);
        }

        return { data, x: xArr, y: yArr }
    }

    private getScale(range: [number, number], data: any[], scalerOptions: ScalerOptions) {
        switch (scalerOptions.type) {
            case 'Date': {
                const domain = 
                    scalerOptions.domain?.map(item => new Date(item)) as [Date, Date] 
                    ?? d3.extent(data as Date[]) as [Date, Date];

                const scaler = d3.scaleUtc().domain(scalerOptions.isReverse ? domain.toReversed() : domain).range(range);

                if (scalerOptions.isNice) scaler.nice();

                return scaler;
            }
            case 'Liner': {
                const domain = scalerOptions.domain as [number, number] ?? d3.extent(data as number[]) as [number, number];

                const scaler = d3.scaleLinear().domain(scalerOptions.isReverse ? domain.toReversed() : domain).range(range);

                if (scalerOptions.isNice) scaler.nice();

                return scaler;
            }
        }
    }

    private convertValue(value: any, type: string) {
        switch (type) {
            case 'Date':
                return new Date(value);
            case 'Liner':
                return Number(value);
        }
    }
}